package git

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"

	"gitee.com/go-errors/errors"
)

type Describe struct {
	Tag     string
	Hash    Hash
	Branch  string
	Counter int
}

type Hash [20]byte

func (h Hash) String() string {
	return hex.EncodeToString(h[:])
}

func (h Hash) Short() string {
	if s := h.String(); len(s) > 7 {
		return s[:7]
	} else {
		return s
	}
}

func (h Hash) IsZero() bool {
	var empty Hash
	return h == empty
}

func (info Describe) Version(prefix string) string {
	var (
		major      int
		minor      int
		patch      int
		preRelease bytes.Buffer
		metadata   bytes.Buffer
	)

	tag := info.Tag
	if idx := strings.Index(tag, "+"); idx != -1 {
		metadata.WriteString(tag[idx+1:])
		tag = tag[:idx]
	}

	if idx := strings.Index(tag, "-"); idx != -1 {
		preRelease.WriteString(tag[idx+1:])
		tag = tag[:idx]
	}

	rs := strings.SplitN(tag, ".", 3)
	for i := 0; i < 3 && i < len(rs); i++ {
		r := rs[i]
		switch i {
		case 0:
			s := make([]byte, 0, len(r))
			for len(r) > 0 {
				if rj := r[0]; rj >= '0' && rj <= '9' {
					s = append(s, rj)
				} else if len(s) > 0 {
					break
				}
				r = r[1:]
			}
			major, _ = strconv.Atoi(string(s))
		case 1:
			minor, _ = strconv.Atoi(r)
		case 2:
			patch, _ = strconv.Atoi(r)
			break
		}
	}

	if info.Counter > 0 {
		if preRelease.Len() > 0 {
			preRelease.WriteByte('.')
		}
		preRelease.WriteString("dev")
		preRelease.WriteByte('.')
		preRelease.WriteString(strconv.Itoa(info.Counter))
		patch++
	}

	if info.Branch != "" || !info.Hash.IsZero() {
		if metadata.Len() > 0 {
			metadata.WriteByte('.')
		}
		metadata.WriteString("commit")
		if info.Branch != "" {
			metadata.WriteByte('.')
			metadata.WriteString(info.Branch)
		}
		if !info.Hash.IsZero() {
			metadata.WriteByte('.')
			metadata.WriteString(info.Hash.Short())
		}
	}

	var ps string
	if preRelease.Len() > 0 {
		ps = "-" + preRelease.String()
	}

	var ms string
	if metadata.Len() > 0 {
		ps = "+" + metadata.String()
	}

	return fmt.Sprintf("%s%d.%d.%d%s%s", prefix, major, minor, patch, ps, ms)
}

func (info Describe) String() string {
	return info.Version("v")
}

func GetDescribe(path string) (describe Describe, err error) {
	repo, err := Open(path)
	if err != nil {
		return describe, errors.Wrap(err)
	}

	describe, err = getDescribe(repo)
	return describe, errors.Wrap(err)
}
